msg_tool\scripts\circus\archive/
dat.rs

1//! Circus Archive File (.dat)
2use crate::ext::io::*;
3use crate::scripts::base::*;
4use crate::types::*;
5use anyhow::Result;
6use std::io::{Read, Seek, SeekFrom};
7use std::sync::{Arc, Mutex};
8
9#[derive(Debug)]
10/// Circus DAT Archive Builder
11pub struct DatArchiveBuilder {}
12
13impl DatArchiveBuilder {
14    /// Creates a new instance of `DatArchiveBuilder`.
15    pub fn new() -> Self {
16        Self {}
17    }
18}
19
20impl ScriptBuilder for DatArchiveBuilder {
21    fn default_encoding(&self) -> Encoding {
22        Encoding::Cp932
23    }
24
25    fn default_archive_encoding(&self) -> Option<Encoding> {
26        Some(Encoding::Cp932)
27    }
28
29    fn build_script(
30        &self,
31        data: Vec<u8>,
32        _filename: &str,
33        _encoding: Encoding,
34        archive_encoding: Encoding,
35        config: &ExtraConfig,
36        _archive: Option<&Box<dyn Script>>,
37    ) -> Result<Box<dyn Script + Send + Sync>> {
38        Ok(Box::new(DatArchive::new(
39            MemReader::new(data),
40            archive_encoding,
41            config,
42        )?))
43    }
44
45    fn build_script_from_file(
46        &self,
47        filename: &str,
48        _encoding: Encoding,
49        archive_encoding: Encoding,
50        config: &ExtraConfig,
51        _archive: Option<&Box<dyn Script>>,
52    ) -> Result<Box<dyn Script + Send + Sync>> {
53        if filename == "-" {
54            let data = crate::utils::files::read_file(filename)?;
55            Ok(Box::new(DatArchive::new(
56                MemReader::new(data),
57                archive_encoding,
58                config,
59            )?))
60        } else {
61            let f = std::fs::File::open(filename)?;
62            let reader = std::io::BufReader::new(f);
63            Ok(Box::new(DatArchive::new(reader, archive_encoding, config)?))
64        }
65    }
66
67    fn build_script_from_reader<'a>(
68        &self,
69        reader: Box<dyn ReadSeek + Send + Sync + 'a>,
70        _filename: &str,
71        _encoding: Encoding,
72        archive_encoding: Encoding,
73        config: &ExtraConfig,
74        _archive: Option<&Box<dyn Script>>,
75    ) -> Result<Box<dyn Script + Send + Sync + 'a>> {
76        Ok(Box::new(DatArchive::new(reader, archive_encoding, config)?))
77    }
78
79    fn extensions(&self) -> &'static [&'static str] {
80        &["dat"]
81    }
82
83    fn script_type(&self) -> &'static ScriptType {
84        &ScriptType::CircusDat
85    }
86
87    fn is_archive(&self) -> bool {
88        true
89    }
90
91    fn is_this_format(&self, _filename: &str, buf: &[u8], buf_len: usize) -> Option<u8> {
92        is_this_format(&buf[..buf_len]).ok()
93    }
94}
95
96#[derive(Debug, Clone)]
97struct DatFileHeader {
98    name: String,
99    offset: u32,
100    size: u32,
101}
102
103#[derive(Debug)]
104struct Entry<T: Read + Seek> {
105    header: DatFileHeader,
106    reader: Arc<Mutex<T>>,
107    pos: usize,
108    script_type: Option<ScriptType>,
109}
110
111impl<T: Read + Seek + std::fmt::Debug + Send + Sync> ArchiveContent for Entry<T> {
112    fn name(&self) -> &str {
113        &self.header.name
114    }
115
116    fn size(&self) -> Option<u64> {
117        Some(self.header.size as u64)
118    }
119
120    fn script_type(&self) -> Option<&ScriptType> {
121        self.script_type.as_ref()
122    }
123
124    fn to_data<'a>(&'a mut self) -> Result<Box<dyn ReadSeek + Send + Sync + 'a>> {
125        Ok(Box::new(self))
126    }
127}
128
129impl<T: Read + Seek> Read for Entry<T> {
130    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
131        let mut reader = self.reader.lock().map_err(|e| {
132            std::io::Error::new(
133                std::io::ErrorKind::Other,
134                format!("Failed to lock mutex: {}", e),
135            )
136        })?;
137        reader.seek(SeekFrom::Start(self.header.offset as u64 + self.pos as u64))?;
138        let bytes_read = buf.len().min(self.header.size as usize - self.pos);
139        if bytes_read == 0 {
140            return Ok(0);
141        }
142        let bytes_read = reader.read(&mut buf[..bytes_read])?;
143        self.pos += bytes_read;
144        Ok(bytes_read)
145    }
146}
147
148impl<T: Read + Seek> Seek for Entry<T> {
149    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
150        let new_pos = match pos {
151            SeekFrom::Start(offset) => offset as usize,
152            SeekFrom::End(offset) => {
153                if offset < 0 {
154                    if (-offset) as usize > self.header.size as usize {
155                        return Err(std::io::Error::new(
156                            std::io::ErrorKind::InvalidInput,
157                            "Seek from end exceeds file length",
158                        ));
159                    }
160                    self.header.size as usize - (-offset) as usize
161                } else {
162                    self.header.size as usize + offset as usize
163                }
164            }
165            SeekFrom::Current(offset) => {
166                if offset < 0 {
167                    if (-offset) as usize > self.pos {
168                        return Err(std::io::Error::new(
169                            std::io::ErrorKind::InvalidInput,
170                            "Seek from current exceeds current position",
171                        ));
172                    }
173                    self.pos.saturating_sub((-offset) as usize)
174                } else {
175                    self.pos + offset as usize
176                }
177            }
178        };
179        self.pos = new_pos;
180        Ok(self.pos as u64)
181    }
182
183    fn stream_position(&mut self) -> std::io::Result<u64> {
184        Ok(self.pos as u64)
185    }
186}
187
188#[derive(Debug)]
189/// Extra information for the DAT archive.
190pub struct DatExtraInfo {
191    /// Maximum length of file names in the DAT archive.
192    pub name_len: usize,
193}
194
195impl AnyDebug for DatExtraInfo {
196    fn as_any(&self) -> &dyn std::any::Any {
197        self
198    }
199}
200
201#[derive(Debug)]
202/// Circus DAT Archive
203pub struct DatArchive<'b, T: Read + Seek + std::fmt::Debug + 'b> {
204    reader: Arc<Mutex<T>>,
205    entries: Vec<DatFileHeader>,
206    name_len: usize,
207    _mark: std::marker::PhantomData<&'b ()>,
208}
209
210const NAME_LEN: [usize; 3] = [0x24, 0x30, 0x3C];
211
212impl<'b, T: Read + Seek + std::fmt::Debug + 'b> DatArchive<'b, T> {
213    /// Creates a new `DatArchive` from a reader.
214    ///
215    /// * `reader` - The reader to read the DAT archive from.
216    /// * `encoding` - The encoding to use for string fields.
217    /// * `config` - Extra configuration options.
218    pub fn new(mut reader: T, encoding: Encoding, _config: &ExtraConfig) -> Result<Self> {
219        let (name_len, entries) = Self::read_all_index(&mut reader, encoding)?;
220        let reader = Arc::new(Mutex::new(reader));
221        Ok(Self {
222            reader,
223            entries,
224            name_len,
225            _mark: std::marker::PhantomData,
226        })
227    }
228
229    fn read_all_index(reader: &mut T, encoding: Encoding) -> Result<(usize, Vec<DatFileHeader>)> {
230        for &name_len in &NAME_LEN {
231            match Self::read_index(reader, encoding, name_len) {
232                Ok(entries) => return Ok((name_len, entries)),
233                Err(_) => continue,
234            }
235        }
236        Err(anyhow::anyhow!("Failed to read DAT index"))
237    }
238
239    fn read_index(
240        reader: &mut T,
241        encoding: Encoding,
242        name_len: usize,
243    ) -> Result<Vec<DatFileHeader>> {
244        reader.rewind()?;
245        let mut count = reader.read_u32()?;
246        let index_size = (name_len + 4) * count as usize;
247        count -= 1;
248        let mut entries = Vec::with_capacity(count as usize);
249        let mut next_offset = reader.peek_u32_at(4 + name_len as u64)?;
250        if (next_offset as usize) < index_size + 4 {
251            return Err(anyhow::anyhow!("Invalid next_offset"));
252        }
253        let first_size = reader.peek_u32_at(name_len as u64)?;
254        let second_offset = reader.peek_u32_at(8 + name_len as u64 * 2)?;
255        if second_offset - next_offset == first_size {
256            return Err(anyhow::anyhow!("Invalid second_offset"));
257        }
258        let file_len = reader.stream_length()?;
259        for i in 0..count {
260            let name = reader.read_fstring(name_len, encoding, true)?;
261            if name.is_empty() {
262                return Err(anyhow::anyhow!("Empty file name in DAT archive"));
263            }
264            let offset = next_offset;
265            if i + 1 == count {
266                next_offset = file_len as u32;
267            } else {
268                next_offset = reader.peek_u32_at((name_len as u64 + 4) * (i as u64 + 2))?;
269            }
270            if next_offset < offset {
271                return Err(anyhow::anyhow!("Invalid offset in DAT archive"));
272            }
273            let size = next_offset - offset;
274            if offset < index_size as u32 || offset + size > file_len as u32 {
275                return Err(anyhow::anyhow!("Invalid offset or size in DAT archive"));
276            }
277            let header = DatFileHeader { name, offset, size };
278            entries.push(header);
279            reader.seek_relative(4)?;
280        }
281        Ok(entries)
282    }
283}
284
285impl<'b, T: Read + Seek + std::fmt::Debug + Send + Sync + 'b> Script for DatArchive<'b, T> {
286    fn default_output_script_type(&self) -> OutputScriptType {
287        OutputScriptType::Json
288    }
289
290    fn default_format_type(&self) -> FormatOptions {
291        FormatOptions::None
292    }
293
294    fn is_archive(&self) -> bool {
295        true
296    }
297
298    fn iter_archive_filename<'a>(
299        &'a self,
300    ) -> Result<Box<dyn Iterator<Item = Result<String>> + 'a>> {
301        Ok(Box::new(self.entries.iter().map(|e| Ok(e.name.clone()))))
302    }
303
304    fn iter_archive_offset<'a>(&'a self) -> Result<Box<dyn Iterator<Item = Result<u64>> + 'a>> {
305        Ok(Box::new(self.entries.iter().map(|e| Ok(e.offset as u64))))
306    }
307
308    fn open_file<'a>(&'a self, index: usize) -> Result<Box<dyn ArchiveContent + Send + Sync + 'a>> {
309        if index >= self.entries.len() {
310            return Err(anyhow::anyhow!(
311                "Index out of bounds: {} (max: {})",
312                index,
313                self.entries.len()
314            ));
315        }
316        let entry = &self.entries[index];
317        let mut entry = Entry {
318            header: entry.clone(),
319            reader: self.reader.clone(),
320            pos: 0,
321            script_type: None,
322        };
323        let mut buf = [0; 32];
324        let readed = match entry.read(&mut buf) {
325            Ok(readed) => readed,
326            Err(e) => {
327                return Err(anyhow::anyhow!(
328                    "Failed to read entry '{}': {}",
329                    entry.header.name,
330                    e
331                ));
332            }
333        };
334        entry.pos = 0;
335        entry.script_type = detect_script_type(&buf, readed, &entry.header.name);
336        Ok(Box::new(entry))
337    }
338
339    fn extra_info<'a>(&'a self) -> Option<Box<dyn AnyDebug + 'a>> {
340        Some(Box::new(DatExtraInfo {
341            name_len: self.name_len,
342        }))
343    }
344}
345
346fn detect_script_type(_buf: &[u8], _buf_len: usize, _filename: &str) -> Option<ScriptType> {
347    #[cfg(feature = "circus-img")]
348    if _buf_len >= 4 && _buf.starts_with(b"CRXG") {
349        return Some(ScriptType::CircusCrx);
350    }
351    #[cfg(feature = "circus-audio")]
352    if _buf_len >= 4 && _buf.starts_with(b"XPCM") {
353        return Some(ScriptType::CircusPcm);
354    }
355    None
356}
357
358fn is_this_format_name_len(buf: &[u8], name_len: usize) -> Result<u8> {
359    let mut reader = MemReaderRef::new(buf);
360    let count = reader.read_u32()? as usize;
361    let index_size = (name_len + 4) * count;
362    let mut score = if count > 0 && count < 1000 { 5 } else { 0 };
363    let mcount = ((buf.len() - 4) / (name_len + 4)).min(count - 1);
364    score += ((mcount / 2).min(10)) as u8;
365    if mcount == 0 {
366        return Err(anyhow::anyhow!("No entries found in DAT archive"));
367    }
368    let mut next_offset = reader.cpeek_u32_at(4 + name_len as u64)?;
369    if (next_offset as usize) < index_size + 4 {
370        return Err(anyhow::anyhow!("Invalid next_offset in DAT archive"));
371    }
372    let first_size = reader.cpeek_u32_at(name_len as u64)?;
373    let second_offset = reader.cpeek_u32_at(8 + name_len as u64 * 2)?;
374    if second_offset < next_offset || second_offset - next_offset == first_size {
375        return Err(anyhow::anyhow!("Invalid second_offset in DAT archive"));
376    }
377    for i in 0..mcount {
378        let offset = next_offset;
379        if i + 1 == mcount {
380            break;
381        } else {
382            next_offset = reader.cpeek_u32_at((name_len as u64 + 4) * (i as u64 + 2))?;
383        }
384        if next_offset < offset {
385            return Err(anyhow::anyhow!("Invalid offset in DAT archive"));
386        }
387        if offset < index_size as u32 {
388            return Err(anyhow::anyhow!(
389                "Offset is less than index size in DAT archive"
390            ));
391        }
392    }
393    Ok(score)
394}
395
396/// Checks if the buffer is a valid DAT archive format.
397///
398/// * `buf` - The buffer to check.
399pub fn is_this_format(buf: &[u8]) -> Result<u8> {
400    for &name_len in &NAME_LEN {
401        match is_this_format_name_len(buf, name_len) {
402            Ok(score) => return Ok(score),
403            Err(_) => continue,
404        }
405    }
406    Err(anyhow::anyhow!("Not a valid DAT archive format"))
407}